/**
 * Copyright (C) Olafur Gauti Gudmundsson
 *
 * Licensed under the Apache License, Version 2.0 (the "License");
 * you may not use this file except in compliance with the License.
 * You may obtain a copy of the License at
 *
 * http://www.apache.org/licenses/LICENSE-2.0
 *
 * Unless required by applicable law or agreed to in writing, software
 * distributed under the License is distributed on an "AS IS" BASIS,
 * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
 * See the License for the specific language governing permissions and
 * limitations under the License.
 */
package org.solrom;

import java.lang.reflect.Field;
import java.lang.reflect.Method;
import java.lang.reflect.Type;
import java.util.ArrayList;
import java.util.Arrays;
import java.util.Date;
import java.util.List;
import org.solrom.annotations.IndexDocument;

/**
 * Various reflection utility methods.
 * 
 * @author Olafur Gauti Gudmundsson
 */
class Utils {
	
	/**
	 * Get an array of all fields declared in the supplied class,
	 * and all its superclasses (except java.lang.Object).
	 * 
	 * @param type the class for which we want to retrieve the Fields
	 * @return an array of all declared and inherited fields
	 */
	static Field[] getDeclaredAndInheritedFields( Class c ) {
		List<Field> allFields = new ArrayList<Field>();
		allFields.addAll(Arrays.asList(c.getDeclaredFields()));
		Class parent = c.getSuperclass();
		while ( parent != null && parent != Object.class ) {
			allFields.addAll(Arrays.asList(parent.getDeclaredFields()));
			parent = parent.getSuperclass();
		}
		return allFields.toArray(new Field[allFields.size()]);
	}
	
	/**
	 * Check whether the method has a valid return type, that is, it is not
	 * void and the return type is a simple type.
	 * 
	 * @param method the method to check
	 * @return true if the return type is valid, else false
	 */
	static boolean hasValidReturnType( Method method ) {
		Class returnType = method.getReturnType();
		return returnType != null && isSimpleType(returnType);
	}
	
	/**
	 * Check whether the method has a valid argument, that is, it has a
	 * single parameter which is of a simple type.
	 *
	 * @param method the method to check
	 * @return true if the arguments are valid, else false
	 */
	static boolean hasValidArgument( Method method ) {
		Type[] paramTypes = method.getGenericParameterTypes();
		if ( paramTypes == null || paramTypes.length > 1 ) {
			return false;
		}
		return isSimpleType((Class)paramTypes[0]);
	}
	
	/**
	 * Check if the class supplied is one of the classes supported
	 * for Solrom mapping.
	 * 
	 * @param c the class to check
	 * @return true if the class is of a supported type, else false
	 */
	static boolean isSimpleType( Class c ) {
		return c == String.class
			|| c == int.class || c == Integer.class
			|| c == long.class || c == Long.class
			|| c == double.class || c == Double.class
			|| c == boolean.class || c == Boolean.class
			|| c == float.class || c == Float.class
            || c == Date.class
            || c.isEnum()
			;
	}
	
	/**
	 * Check whether the method supplied is a "getter" method, by inspecting
	 * its name.
	 * 
	 * @param method the method to check
	 * @return true if the name of the method suggests a getter, else false
	 */
	static boolean isGetter( Method method ) {
		return (method.getName().startsWith("get") && method.getName().length() > 3)
				|| (method.getName().startsWith("is") && method.getName().length() > 2);
	}
	
	/**
	 * Check whether the method supplied is a "setter" method, by inspecting
	 * its name.
	 * 
	 * @param method the method to check
	 * @return true if the name of the method suggests a setter, else false
	 */
	static boolean isSetter( Method method ) {
		return method.getName().startsWith("set") && method.getName().length() > 3;
	}
	
	/**
	 * Get the name of the getter property for this method.
	 * 
	 * @param method the method in question
	 * @return the name of the property being get by this method
	 */
	static String getGetterPropertyName( Method method ) {
		if ( method.getName().startsWith("is") ) {
			return getPropertyName(method, "is");
		} else {
			return getPropertyName(method, "get");
		}
	}
	
	/**
	 * Get the name of the setter property for this method.
	 * 
	 * @param method the method in question
	 * @return the name of the property being set by this method
	 */
	static String getSetterPropertyName( Method method ) {
		return getPropertyName(method, "set");
	}

	/**
	 * Get the property name from the method name, by removing the supplied
	 * prefix first.
	 * 
	 * @param method the method in question
	 * @param methodPrefix the prefix to be removed
	 * @return the property name retrieved from the method name
	 */
	private static String getPropertyName( Method method, String methodPrefix ) {
		int prefixLength = methodPrefix.length();
		if ( method.getName().startsWith(methodPrefix)
				&& method.getName().length() > prefixLength ) {
			
			char firstChar = method.getName().charAt(prefixLength);
			if ( Character.isUpperCase(firstChar) ) {
				if ( method.getName().length() > (prefixLength+1) ) {
					return Character.toLowerCase(firstChar) + method.getName().substring(prefixLength+1);
				} else {
					return Character.toString(Character.toLowerCase(firstChar));
				}
			} else {
				return method.getName();
			}
		} else {
			// this is neither a setter nor getter
			return method.getName();
		}
	}
	
	/**
	 * Get the class-level @IndexDocument annotation from the class supplied.
	 * Also checks superclasses and implemented interfaces.
	 * 
	 * @param c the class to process
	 * @return the @IndexDocument annotation, or null if none is found
	 */
	static IndexDocument getIndexDocumentAnnotation( Class c ) {
		
		if ( c.isAnnotationPresent(IndexDocument.class) ) {
			return (IndexDocument) c.getAnnotation(IndexDocument.class);
		} else {
			// need to check all superclasses
			Class parent = c.getSuperclass();
			while ( parent != null && parent != Object.class ) {
				if ( parent.isAnnotationPresent(IndexDocument.class) ) {
					return (IndexDocument) parent.getAnnotation(IndexDocument.class);
				}
				parent = parent.getSuperclass();
			}
			
			// ...and all implemented interfaces
			for ( Class interfaceClass : c.getInterfaces() ) {
				if ( interfaceClass.isAnnotationPresent(IndexDocument.class) ) {
					return (IndexDocument) interfaceClass.getAnnotation(IndexDocument.class);
				}
			}
		}
		// no annotation found, use the defaults
		return null;
	}
}
